/******************************************************************************
 * Copyright 2022 The AIR Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#pragma once
#include <memory>
#include <string>
#include <utility>

#include "cw/common.hpp"
namespace cw {

AsymCipher::AsymCipher(KeyPair kp, int meth_type)
    : kp_(std::move(kp)),
      meth_(meth_type > 0 ? EVP_PKEY_meth_find(meth_type) : nullptr) {
  if (meth_) {
    EVP_PKEY_meth_get_sign(meth_, &func_sign_init_, &func_sign_);
    EVP_PKEY_meth_get_verify(meth_, &func_verify_init_, &func_verify_);
    EVP_PKEY_meth_get_encrypt(meth_, &func_encrypt_init_, &func_encrypt_);
    EVP_PKEY_meth_get_decrypt(meth_, &func_decrypt_init_, &func_decrypt_);
  }
}

std::string AsymCipher::Encipher(const std::string &data) const {
  if (!func_encrypt_) {
    std::cerr << "[CW] Unsupported encrypt" << std::endl;
    return "";
  }
  auto ctx = std::shared_ptr<EVP_PKEY_CTX>(
      EVP_PKEY_CTX_new(const_cast<EVP_PKEY *>(kp_.NativeHandle()), nullptr),
      EVP_PKEY_CTX_free);
  if (!ctx) {
    std::cerr << "[CW] Failed to create EVP_PKEY_CTX" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  if (func_encrypt_init_ && 0 >= func_encrypt_init_(ctx.get())) {
    std::cerr << "[CW] Failed to init encrypt!" << std::endl;
    return "";
  }
  char buffer[kBufferSize] = {};
  size_t buflen = kBufferSize;

  if (0 >= func_encrypt_(ctx.get(), reinterpret_cast<unsigned char *>(buffer),
                         &buflen,
                         reinterpret_cast<const unsigned char *>(data.c_str()),
                         data.length())) {
    std::cerr << "[CW] Failed to encrypt!" << std::endl;
    return "";
  }
  return {buffer, buflen};
}

std::string AsymCipher::Decipher(const std::string &data) const {
  if (!func_decrypt_) {
    std::cerr << "[CW] Unsupported decrypt" << std::endl;
    return "";
  }
  auto ctx = std::shared_ptr<EVP_PKEY_CTX>(
      EVP_PKEY_CTX_new(const_cast<EVP_PKEY *>(kp_.NativeHandle()), nullptr),
      EVP_PKEY_CTX_free);
  if (!ctx) {
    std::cerr << "[CW] Failed to create EVP_PKEY_CTX" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  if (func_decrypt_init_ && 0 >= func_decrypt_init_(ctx.get())) {
    std::cerr << "[CW] Failed to init encrypt!" << std::endl;
    return "";
  }
  char buffer[kBufferSize] = {};
  size_t buflen = kBufferSize;

  if (0 >= func_decrypt_(ctx.get(), reinterpret_cast<unsigned char *>(buffer),
                         &buflen,
                         reinterpret_cast<const unsigned char *>(data.c_str()),
                         data.length())) {
    std::cerr << "[CW] Failed to decrypt!" << std::endl;
    return "";
  }
  return {buffer, buflen};
}

std::string AsymCipher::Sign(const std::string &data) const {
  if (!func_sign_) {
    std::cerr << "[CW] Unsupported Sign" << std::endl;
    return "";
  }
  auto ctx = std::shared_ptr<EVP_PKEY_CTX>(
      EVP_PKEY_CTX_new(const_cast<EVP_PKEY *>(kp_.NativeHandle()), nullptr),
      EVP_PKEY_CTX_free);
  if (!ctx) {
    std::cerr << "[CW] Failed to create EVP_PKEY_CTX" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  if (func_sign_init_ && 0 >= func_sign_init_(ctx.get())) {
    std::cerr << "[CW] Failed to init sign!" << std::endl;
    return "";
  }
  char buffer[kBufferSize] = {};
  size_t buflen = kBufferSize;

  if (0 >= func_sign_(ctx.get(), reinterpret_cast<unsigned char *>(buffer),
                      &buflen,
                      reinterpret_cast<const unsigned char *>(data.c_str()),
                      data.length())) {
    std::cerr << "[CW] Failed to sign!" << std::endl;
    return "";
  }
  return {buffer, buflen};
}

bool AsymCipher::Verify(const std::string &data,
                        const std::string &signature) const {
  if (!func_verify_) {
    std::cerr << "[CW] Unsupported Verify" << std::endl;
    return false;
  }
  auto ctx = std::shared_ptr<EVP_PKEY_CTX>(
      EVP_PKEY_CTX_new(const_cast<EVP_PKEY *>(kp_.NativeHandle()), nullptr),
      EVP_PKEY_CTX_free);
  if (!ctx) {
    std::cerr << "[CW] Failed to create EVP_PKEY_CTX" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return false;
  }
  if (func_verify_init_ && 0 >= func_verify_init_(ctx.get())) {
    std::cerr << "[CW] Failed to init verify!" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return false;
  }
  if (0 >=
      func_verify_(ctx.get(),
                   reinterpret_cast<const unsigned char *>(signature.c_str()),
                   signature.length(),
                   reinterpret_cast<const unsigned char *>(data.c_str()),
                   data.length())) {
    std::cerr << "[CW] Failed to verify!" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return false;
  }
  return true;
}
}  // namespace cw
